#ifndef COMMON_MATHS_VECTOR4_H
#define COMMON_MATHS_VECTOR4_H


#include "Vector3.h"


class Vector4;
typedef const Vector4& Vector4Arg;


Vector4 Abs(Vector4Arg v);
float   Dot3(Vector4Arg a, Vector4Arg b);
float   Dot4(Vector4Arg a, Vector4Arg b);
float   LengthSqr3(Vector4Arg v);
float   Length3(Vector4Arg v);
float   LengthSqr4(Vector4Arg v);
float   Length4(Vector4Arg v);
Vector4 Normalize3(Vector4Arg v); // w is unchanged
Vector4 Normalize4(Vector4Arg v);
Vector4 SafeNormalize3(Vector4Arg v, Vector3Arg fallback); // w is unchanged
Vector4 SafeNormalize4(Vector4Arg v, Vector4Arg fallback); // w = 0
Vector4 Lerp(Vector4Arg a, Vector4Arg b, float t);


class Vector4
{
public:
    enum ZeroTag { kZero };

    float       x;
    float       y;
    float       z;
    float       w;
    
                Vector4();
                Vector4(const ZeroTag&);
                Vector4(Vector3Arg v, float w);
                Vector4(float x, float y, float z, float w);
    
    Vector4     operator+(Vector4Arg v) const;
    Vector4     operator-(Vector4Arg v) const;
    Vector4     operator*(Vector4Arg v) const;
    Vector4     operator*(float f) const;
    Vector4     operator/(float f) const;
    
    Vector4&    operator+=(Vector4Arg v);
    Vector4&    operator-=(Vector4Arg v);
    Vector4&    operator*=(Vector4Arg v);
    Vector4&    operator*=(float f);
    Vector4&    operator/=(Vector4Arg v);
    Vector4&    operator/=(float f);

    Vector4     operator-() const;

    Vector3Arg  AsVector3() const;
};


inline Vector4::Vector4()
{
}

inline Vector4::Vector4(const ZeroTag&)
    : x(0.0f), y(0.0f), z(0.0f), w(0.0f) { }

inline Vector4::Vector4(Vector3Arg v, float w)
    : x(v.x), y(v.y), z(v.z), w(w) { }

inline Vector4::Vector4(float x, float y, float z, float w)
    : x(x), y(y), z(z), w(w) { }

inline Vector4 Vector4::operator+(Vector4Arg v) const
{
    return Vector4(x+v.x, y+v.y, z+v.z, w+v.w);
}

inline Vector4 Vector4::operator-(Vector4Arg v) const
{
    return Vector4(x-v.x, y-v.y, z-v.z, w-v.w);
}

inline Vector4 Vector4::operator*(Vector4Arg v) const
{
    return Vector4(x*v.x, y*v.y, z*v.z, w*v.w);
}

inline Vector4 Vector4::operator*(float f) const
{
    return Vector4(f*x, f*y, f*z, f*w);
}

inline Vector4 Vector4::operator/(float f) const
{
    const float r = 1.0f / f;
    return Vector4(r*x, r*y, r*z, r*w);
}

inline Vector4& Vector4::operator+=(Vector4Arg v)
{
    x += v.x;
    y += v.y;
    z += v.z;
    w += v.w;
    return *this;
}

inline Vector4& Vector4::operator-=(Vector4Arg v)
{
    x -= v.x;
    y -= v.y;
    z -= v.z;
    w -= v.w;
    return *this;
}

inline Vector4& Vector4::operator*=(Vector4Arg v)
{
    x *= v.x;
    y *= v.y;
    z *= v.z;
    w *= v.w;
    return *this;
}

inline Vector4& Vector4::operator*=(float f)
{
    x *= f;
    y *= f;
    z *= f;
    w *= f;
    return *this;
}

inline Vector4& Vector4::operator/=(Vector4Arg v)
{
    x /= v.x;
    y /= v.y;
    z /= v.z;
    w /= v.w;
    return *this;
}

inline Vector4& Vector4::operator/=(float f)
{
    x /= f;
    y /= f;
    z /= f;
    w /= f;
    return *this;
}

inline Vector4 Vector4::operator-() const
{
    return Vector4(-x, -y, -z, -w);
}

inline Vector3Arg Vector4::AsVector3() const
{
    return *(Vector3*)this;
}

inline Vector4 operator*(float f, Vector4Arg v)
{
    return Vector4(f*v.x, f*v.y, f*v.z, f*v.w);
}

inline Vector4 operator/(float f, Vector4Arg v)
{
    return Vector4(f/v.x, f/v.y, f/v.z, f/v.w);
}

inline Vector4 Abs(Vector4Arg v)
{
    return Vector4(abs(v.x), abs(v.y), abs(v.z), abs(v.w));
}

inline float Dot3(Vector4Arg a, Vector4Arg b)
{
    return a.x*b.x + a.y*b.y + a.z*b.z;
}

inline float Dot4(Vector4Arg a, Vector4Arg b)
{
    return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
}

inline float LengthSqr3(Vector4Arg v)
{
    return v.x*v.x + v.y*v.y + v.z*v.z;
}

inline float LengthSqr4(Vector4Arg v)
{
    return v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w;
}

inline float Length3(Vector4Arg v)
{
    return sqrt(LengthSqr3(v));
}

inline float Length4(Vector4Arg v)
{
    return sqrt(LengthSqr4(v));
}

inline Vector4 Normalize3(Vector4Arg v)
{
    const float r = 1.0f / Length3(v);
    return Vector4(v.x*r, v.y*r, v.z*r, v.w);
}

inline Vector4 Normalize4(Vector4Arg v)
{
    return v / Length4(v);
}

inline Vector4 SafeNormalize3(Vector4Arg v, Vector3Arg fallback)
{
    const float len = Length3(v);
    if (len > 0.0f)
    {
        const float r = 1.0f / len;
        return Vector4(v.x*r, v.y*r, v.z*r, v.w);
    }
    else
        return Vector4(fallback.x, fallback.y, fallback.z, v.w);
}

inline Vector4 SafeNormalize4(Vector4Arg v, Vector4Arg fallback)
{
    const float len = Length4(v);
    return (len>0.0f) ? v/len : fallback;
}

inline Vector4 Lerp(Vector4Arg a, Vector4Arg b, float t)
{
    return a + t*(b-a);
}


#endif
